/************************************************************************
 *  Copyright (c) 2011-2012 HONG LEIMING.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to permit
 * persons to whom the Software is furnished to do so, subject to the
 * following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
 * NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
 * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
 * USE OR OTHER DEALINGS IN THE SOFTWARE.
 ***************************************************************************/
package net.zbus.rpc;


import java.util.UUID;

import net.zbus.BusClient;
import net.zbus.ClientPool;
import net.zbus.ZBusException;
import net.zbus.ZMsg;

import org.apache.log4j.Logger;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONException;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SerializerFeature;

public class Rpc { 
	public static final String LANG_JAVA = "java";
	public static final String LANG_KEY = "client";
	public static final String DEFAULT_ENCODING = "UTF-8";

	private static final Logger logger = Logger.getLogger(Rpc.class);
	
	private ClientPool clientPool;

	private String service;
	private String token = "";
	private String module = "";
	
	private String encoding;
	private int timeout = 10000;
	private String remoteLanguage = LANG_JAVA;
	private boolean zbusLog = true;

	public Rpc(ClientPool clientPool, String service) {
		this(clientPool, service, "");
	}
	
	public Rpc(ClientPool clientPool, String service, String module) {
		this.clientPool = clientPool;
		this.service = service;
		this.encoding = DEFAULT_ENCODING;
		this.module = module;
	}
	

	@SuppressWarnings("unchecked")
	public <T> T invoke(String method, Object... args) {

		JSONObject req = new JSONObject();
		req.put("id", "jsonrpc");
		req.put("module", this.module);
		req.put("method", method);
		req.put("debugMsgid", UUID.randomUUID().toString());
//		req.put("params", JSON.toJSON(args));
		req.put("params", args);
		req.put(LANG_KEY, remoteLanguage);
		ZMsg msg = new ZMsg();
		if (LANG_JAVA.equals(remoteLanguage)) {
			msg.pushBack(JSON.toJSONBytes(req,
					this.encoding,
					SerializerFeature.WriteMapNullValue,
					SerializerFeature.WriteClassName));
		} else {
			msg.pushBack(JSON.toJSONBytes(req,
					this.encoding,
					SerializerFeature.WriteMapNullValue));
		}
		if(!DEFAULT_ENCODING.equalsIgnoreCase(this.encoding)){
			msg.pushBack(this.encoding);
		}
		
		BusClient client = null;
		long ellapsed = -1;
		long start = System.currentTimeMillis();
		String logMsg = "";
		try {  
			if(this.zbusLog){
				String request = new String(msg.getFirst(), this.encoding);
				logMsg += "[REQ] "+request;
				logger.debug(logMsg);
			} 
			client = this.clientPool.borrowClient();
			msg = client.request(this.service, this.token, msg, this.timeout);
			
		} catch (Exception e) {  
			logger.error(logMsg);
			throw new ZBusException(e.getMessage());
		} finally {
			ellapsed = System.currentTimeMillis() - start;
			if (client != null) {
				clientPool.returnClient(client);
			} 
		}
		if (msg == null) { 
			throw new ZBusException("json rpc request timeout");
		}
		if (msg.frameSize() != 2) {
			throw new ZBusException("json rpc format error[2 frames required]");
		}
		String status = msg.popFrontStr(); 
		if(this.zbusLog){
			logMsg += "\n      [REP] Cost="+ellapsed+"(ms), "+ "Code="+status+", ";
			logger.debug(logMsg);
		}
		if(!status.equals("200")){
			throw new ZBusException(msg.popFrontStr());
		}
		
		JSONObject res = null;
		String text = "";
		try {
			text = new String(msg.popFront(), this.encoding);
			if(this.zbusLog){
				logMsg += "Message="+text;
				logger.debug(logMsg);
			}
			res = (JSONObject) JSON.parse(text);
			
		} catch (Exception e) {
			text = text.replace("@type", "unkown-class"); //try disable class feature
			try{
				res = (JSONObject) JSON.parse(text);
			} catch (JSONException ex) { 
				new ZBusException("json error: "+ text); 
			}  
			if(res.containsKey("stack_trace")){
				logger.debug(res.getString("stack_trace"));
			}
			
			if(res.containsKey("error")){ 
				JSONObject error = res.getJSONObject("error");
				if(error.containsKey("message")){
					throw new ZBusException(error.getString("message"));
				}
			} 
			throw new ZBusException("json error: "+ text); 
		}
		
		if (res.containsKey("result")){
			return (T)res.get("result");
		} 
		if(res.containsKey("stack_trace")){
			logger.debug(res.getString("stack_trace"));
		}
		
		if(!res.containsKey("error")){ 
			throw new ZBusException("return json invalid");
		} 
		
		Object error = res.get("error"); 
		if(error instanceof RuntimeException){ //exception can be loaded
			throw (RuntimeException)error; 
		}  	
		throw new ZBusException(error.toString());
	}

	public String getService() {
		return service;
	}

	public void setService(String service) {
		this.service = service;
	}

	public String getToken() {
		return token;
	}

	public void setToken(String token) {
		this.token = token;
	}

	public String getEncoding() {
		return encoding;
	}

	public void setEncoding(String encoding) {
		this.encoding = encoding;
	}

	public int getTimeout() {
		return timeout;
	}

	public void setTimeout(int timeout) {
		this.timeout = timeout;
	}

	
	
	public String getModule() {
		return module;
	}

	public void setModule(String module) {
		this.module = module;
	}
	
	public Rpc module(String module) {
		this.module = module;
		return this;
	}
 
	/**
	 * enable JSON class feature
	 */
	public void enableRemoteObject(){
		this.remoteLanguage = LANG_JAVA;
	}
	
	public void disableRemoteObject(){
		this.remoteLanguage = "";
	}

	public boolean isZbusLog() {
		return zbusLog;
	}

	public void setZbusLog(boolean zbusLog) {
		this.zbusLog = zbusLog;
	} 
}
